:- module(iop_sib_access_low,
          [ join/2,            % +InputStream, +OutputStream
            query/3,           % +InputStream, +OutputStream, -Triples
            subscribe/3,       % +InputStream, +OutputStream, -Triples
            process_subscription/3,
                               % +InputStream, -NewTriples, -ObsoleteTriples
            unsubscribe/2,     % +InputStream, +OutputStream
            insert/3,          % +InputStream, +OutputStream, +TripleOrTripleListStructure
            remove/3,          % +InputStream, +OutputStream, +TripleOrTripleListStructure
            leave/2,           % +InputStream, +OutputStream

	    close_connection/2 % +InputStream, +OutputStream
          ]).


%%%
%%% helper function for convenience
%%%

close_connection(In, Out) :-
        close(In, [force(true)]),
        close(Out, [force(true)]).

%%%
%%% low level Smart-M3 message handling stuff
%%%

join(In, Out) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,[element('SSAP_message',  [],
	                       [
			         element(transaction_type,[],['JOIN']),
				 element(message_type,[],['REQUEST']),
				 element(transaction_id,[],['11']),
				 element(node_id,[],[KPName]),
				 element(space_id,[],[SmartSpaceName])
                               ])],
		  [header(false),layout(false)]),
	flush_output(Out),
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['JOIN']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['11']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
        !,
	writeln('###'),
	write('### Join: '),
	writeln(Status),
	writeln('###').

leave(In, Out) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,[element('SSAP_message',  [],
	                       [
			         element(transaction_type,[],['LEAVE']),
				 element(message_type,[],['REQUEST']),
				 element(transaction_id,[],['12']),
				 element(node_id,[],[KPName]),
				 element(space_id,[],[SmartSpaceName])
                               ])],
		  [header(false),layout(false)]),
	flush_output(Out),
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['LEAVE']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['12']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
        !,
	writeln('###'),
	write('### Leave: '),
	writeln(Status),
	writeln('###').

query(In, Out, Triples) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	cached_element_list(ElementList),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['QUERY']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['13']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=type],['RDF-M3']),
			element(parameter, [name=query],
		        [ElementList])
		      ]),
		      [header(false),layout(true)]),
	flush_output(Out),	
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['QUERY']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['13']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status),
			 element(parameter,[name=results],Triples)],
		       Structure),
	!,
        writeln('###'),
	write('### Query: '),
	writeln(Status),
	writeln('###').

% helper function to generate a triplelist structure for the
% query/subscribe

cached_element_list(ElementList) :-
	findall(
		element(triple,[],[
	              element(subject,[],[S]),
		      element(predicate,[],[P]),
		      element(object,[],[O])
		]),
		cached(triple(S,P,O)),
		Elements),
	ElementList = element(triple_list, [], Elements).

subscribe(In, Out, Triples) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	cached_element_list(ElementList),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['SUBSCRIBE']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['13']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=type],['RDF-M3']),
			element(parameter, [name=query],
		        [ElementList])
		      ]),
		      [header(false),layout(true)]),
	flush_output(Out),
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['SUBSCRIBE']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['13']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status),
			 element(parameter,[name=subscription_id],
			         [SubscriptionId]),
			 element(parameter,[name=results],Triples)],
		       Structure),
	!,
	assert(iop_sib_access:subscription(SubscriptionId,In,Out)),
	
        writeln('###'),
	write('### Subscription: '),
	writeln(Status),
	writeln('###'),

	writeln('### reopening socket to SIB'),

	iop_sib_access:sib(Host,_,Port),
	setup_call_catcher_cleanup(tcp_socket(Socket),
                                   tcp_connect(Socket, Host:Port),
				   exception(_),
                                   tcp_close_socket(Socket)),
	tcp_open_socket(Socket, NewIn, NewOut),

	retract(iop_sib_access:sib_socket(In,Out)),
	assert(iop_sib_access:sib_socket(NewIn,NewOut)),
	writeln('###').



% when subscribed to the SIB, process_subscription must be called 
% quite frequently:
% currently, it fails if the SIB sends indications too fast, and more 
% than a single SSAP_message tag is available from the stream (Response
% needs to be partitioned and processed individually). 
% (It can be detected though, and repaired by a simple re-subscribing)
process_subscription(In,New,Obsolete) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	receive_response(In, Response),!,
	parse_response(Response,Structure),
    (
	check_response([ element(transaction_type,[],['SUBSCRIBE']),
	                 element(message_type,[],['INDICATION']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=subscription_id],
			         [SubscriptionId]),
			 element(parameter,[name=new_results],New),
			 element(parameter,[name=obsolete_results],Obsolete)],
		       Structure),
	!,
	iop_sib_access:subscription(SubscriptionId,_,_),
        writeln('###'),
	writeln('### Subscription indication: '),
	writeln(add+New),
	writeln(del-Obsolete),
	writeln('###')
    ;
	check_response([ element(transaction_type,[],['UNSUBSCRIBE']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['15']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status),
			 element(parameter,[name=subscription_id],
			         [SubscriptionId])
		       ],
		       Structure),
	!,
	retract(iop_sib_access:subscription(SubscriptionId,In,Out)),
	close_connection(In,Out),
        writeln('###'),
	write('### Unsubscription: '),
	writeln(Status),
	writeln('###')
    ),!.


unsubscribe(In, Out) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	iop_sib_access:subscription(SubscriptionId,_,_),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['UNSUBSCRIBE']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['15']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=subscription_id],
			        [SubscriptionId])
		      ]),
		      [header(false),layout(true)]),
	flush_output(Out),
%receive needed with adk sib, but not needed with c-sib (?)
%	receive_response(In, Response),
	!.

insert(In,Out,element(triple,Arg,Content)) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['INSERT']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['14']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=confirm],['TRUE']),
			element(parameter, [name=insert_graph, encoding='RDF-M3'],
		        [
		          element(triple_list,[],
		                  [element(triple,Arg,Content)])
			])
		      ]),
		      [header(false),layout(false)]),
	flush_output(Out),	
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['INSERT']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['14']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
	!,
	writeln('###'),
	write('### Insert: '),
	writeln(Status),
	writeln('###').

insert(In,Out,element(triple_list,Arg,Content)) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['INSERT']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['14']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=confirm],['TRUE']),
			element(parameter, [name=insert_graph, encoding='RDF-M3'],
		        [
		          element(triple_list,Arg,Content)
			])
		      ]),
		      [header(false),layout(false)]),
	flush_output(Out),	
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['INSERT']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['14']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
	!,
	writeln('###'),
	write('### Insert: '),
	writeln(Status),
	writeln('###').

remove(In,Out,element(triple,Arg,Content)) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['REMOVE']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['15']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=confirm],['TRUE']),
			element(parameter, [name=remove_graph, encoding='RDF-M3'],
		        [
		          element(triple_list,[],
		                  [element(triple,Arg,Content)])
			])
		      ]),
		      [header(false),layout(true)]),
	flush_output(Out),	
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['REMOVE']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['15']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
	!,
	writeln('###'),
	write('### Remove: '),
	writeln(Status),
	writeln('###').	

remove(In,Out,element(triple_list,Arg,Content)) :-
	kp_name(KPName),
	iop_sib_access:sib(_,SmartSpaceName,_),
	xml_write(Out,element('SSAP_message',[],
	              [
	                element(transaction_type,[],['REMOVE']),
			element(message_type,[],['REQUEST']),
			element(transaction_id,[],['15']),
			element(node_id,[],[KPName]),
			element(space_id,[],[SmartSpaceName]),
			element(parameter, [name=confirm],['TRUE']),
			element(parameter, [name=remove_graph, encoding='RDF-M3'],
		        [
			  element(triple_list,Arg,Content)
			])	
		      ]),
		      [header(false),layout(true)]),
	flush_output(Out),	
	receive_response(In, Response),
	parse_response(Response,Structure),
	check_response([ element(transaction_type,[],['REMOVE']),
	                 element(message_type,[],['CONFIRM']),
			 element(transaction_id,[],['15']),
			 element(node_id,[],[KPName]),
			 element(space_id,[],[SmartSpaceName]),
			 element(parameter,[name=status],Status)],
		       Structure),
	!,
	writeln('###'),
	write('### Remove: '),
	writeln(Status),
	writeln('###').	

%
% Receive a response from the SIB.
% Unfortunately we can not directly parse from the socket using 
% load_xml_file(In,Term), because the C-SIB doesn't terminate with a 
% newline and the parser then blocks, so we need a low level read 
% loop and scan for the end tag before we can parse.
%

receive_response(In, String) :-
	string_to_list(Empty, ""),
	receive_until_end(In, Empty, String).

receive_until_end(In, SoFar, All) :-
	\+at_end_of_stream(In),
	read_pending_input(In, SomeCodes, []),
	string_to_list(NowGot,SomeCodes),
	string_concat(SoFar,NowGot,NowHave),
	string_to_list(EndTag,"</SSAP_message>"),

	(sub_string(NowHave, _, _, _, EndTag)
    ->
	!,
	All = NowHave
    ;
	receive_until_end(In, NowHave, All)
    ).


%%first implementation - actually can hang, due to difference lists being not 
%%checked in 'enough/2': Buffering unexpectedly adds to Rest! ( R != RR ) 
%receive_response(In, String) :-
%	writeln('about to receive'),
%	receive_codes(In,Codes-[]),
%	writeln('Got bytes...'),
%	!,
%	string_to_list(String, Codes),
%	writeln(String).
%
%receive_codes(In, Codes-End) :-
%	\+at_end_of_stream(In),
%	read_pending_input(In, Codes, Rest),
%%	writeln('got a slab'),
%	(enough(Codes,Rest)
%    ->
%	writeln('enough - that is all'),
%	!,
%	Rest = End,
%        writeln('last chunk:'),
%	writeln(Codes)
%
%    ;
%	receive_codes(In, Rest-End)
%    ).
%
% Check if we have read enough by scanning for the end tag '</SSAP_message>'
%
% There is a possibility that scanning for the end tag will not work,
% namely if it is not completely within one packet from the socket, for
% example because of payload length limits. I haven't seen that occur
% yet, though.
%enough(L,_) :-
%	var(L),
%	!,fail.
%enough([60, 47, 83, 83, 65, 80, 95, 109, 101, 115, 115, 97, 103, 101, 62|R],RR) :-
%	write('Found tag, rest: '),write(R), writeln(RR),!.
%enough([_|L],R) :-
%	enough(L,R).

% parse the response from XML to a prolog structure
% Note: this all is way more complicated as it should be: 
% - The SIB doesn't end a response with a newline, so parsing 
%   directly from the socket doesn't work.
% - There is no construct to parse from memory (only files and
%   streams), so we have to write and read to a temporary file.

parse_response(Response, Structure) :-
	tell('tmpfile'), writeln(Response), told,
	load_xml_file('tmpfile',Structure).

check_response([],_) :- !.
check_response([H|T],[element(SSAP_message, [],L)]) :-
	member(H,L),
	!,
	check_response(T,[element(SSAP_message, [],L)]).

